package jmine.tec.purge.api;

import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

import bancosys.tec.persist.dao.BaseDAO;
import bancosys.tec.persist.dao.BeanNotFoundException;
import bancosys.tec.utils.date.Date;
import bancosys.tec.utils.date.DateUtils;

/**
 * Metodos utilitários para expurgo.
 * 
 * @author Marcio Ribeiro (mmr)
 * @created Aug 17, 2011
 */
public final class PurgeHelper {

    private static final int MAX_IN_CLAUSE_ENTRIES = 900;

    /**
     * Construtor.
     */
    private PurgeHelper() {
        // nothing
    }

    /**
     * Devolve lista de ids para expurgo de acordo com parametros passados. Assume que campo de data eh um timestamp.
     * 
     * @param crit criteria de entidade a ser expurgada.
     * @param dtField campo de data a usar para comparacao.
     * @param maxDate data maxima.
     * @param maxWindowSize maximo de itens por janela.
     * @return lista de ids para expurgo.
     */
    public static List<Long> getIds(Criteria crit, String dtField, Date maxDate, int maxWindowSize) {
        return getIds(crit, dtField, maxDate, maxWindowSize, true);
    }

    /**
     * Devolve lista de ids para expurgo de acordo com parametros passados.
     * 
     * @param crit criteria de entidade a ser expurgada.
     * @param dtField campo de data a usar para comparacao.
     * @param maxDate data maxima.
     * @param maxWindowSize maximo de itens por janela.
     * @param useTimestamp usar data como timestamp.
     * @return lista de ids para expurgo.
     */
    public static List<Long> getIds(Criteria crit, String dtField, Date maxDate, int maxWindowSize, boolean useTimestamp) {
        crit.setProjection(Projections.id());

        if (useTimestamp) {
            crit.add(Restrictions.le(dtField, DateUtils.createTimestampWithLastSecondOfTheDay(maxDate)));
        } else {
            crit.add(Restrictions.le(dtField, maxDate));
        }
        crit.setMaxResults(maxWindowSize).addOrder(Order.desc(dtField));

        @SuppressWarnings("unchecked")
        List<Long> ids = crit.list();
        return ids;
    }

    /**
     * Devolve quantidade de itens a serem expurgados até a data passada. Assume que campo de data eh um timestamp.
     * 
     * @param crit criteria de entidade.
     * @param dtField nome de campo de data.
     * @param maxDate data maxima a considerar.
     * @return contagem de itens a serem expurgados até a data passada.
     */
    public static int countItemsToPurge(Criteria crit, String dtField, Date maxDate) {
        return countItemsToPurge(crit, dtField, maxDate, true);
    }

    /**
     * Devolve quantidade de itens a serem expurgados até a data passada.
     * 
     * @param crit criteria de entidade.
     * @param dtField nome de campo de data.
     * @param maxDate data maxima a considerar.
     * @param useTimestamp usar data como timestamp.
     * @return contagem de itens a serem expurgados até a data passada.
     */
    public static int countItemsToPurge(Criteria crit, String dtField, Date maxDate, boolean useTimestamp) {
        crit.setProjection(Projections.rowCount());
        if (useTimestamp) {
            crit.add(Restrictions.le(dtField, DateUtils.createTimestampWithLastSecondOfTheDay(maxDate)));
        } else {
            crit.add(Restrictions.le(dtField, maxDate));
        }
        return ((Integer) crit.list().get(0)).intValue();
    }

    /**
     * Cria e devolve a query de delete para expurgo com os dados passados. A query criada terá o formato: "DELETE FROM entidade WHERE id IN
     * (...). Ha tratamento para contornar o problema do numero maximo de entradas dentro da clausula IN (...). No caso do limite
     * 'MAX_IN_CLAUSE_ENTRIES' for atingido, um novo IN sera criado.
     * 
     * @param entityName nome da entidade.
     * @param ids lista com ids a serem expurgados.
     * @return query de delete de expurgo com os dados passados.
     */
    public static String createDeleteQuery(String entityName, List<Long> ids) {
        StringBuilder q = new StringBuilder("delete from ").append(entityName).append(" where (0=1");

        final int maxInClause = Math.min(MAX_IN_CLAUSE_ENTRIES, ids.size());
        for (int i = 0; i < ids.size(); i += maxInClause) {
            q.append(" or id in (");
            q.append(StringUtils.join(ids.subList(i, Math.min(ids.size(), i + maxInClause)), ", "));
            q.append(")");
        }
        q.append(")");

        return q.toString();
    }

    /**
     * Exclui entradas usando persister. Deve ser usado quando nao for possivel excluir entidades usando bulk delete.
     * 
     * @param dao dao para encontrar entidades por id.
     * @param crit criteria para encontrar ids da janela.
     * @param dtField campo de data a usar para comparacao.
     * @param maxDate data de corte.
     * @param maxWindowSize tamanho da janela de expurgo.
     * @param useTimestamp usar data como timestamp.
     * @return itens excluidos.
     */
    public static int deleteWithPersister(BaseDAO<?> dao, Criteria crit, String dtField, Date maxDate, int maxWindowSize,
            boolean useTimestamp) {
        List<Long> ids = PurgeHelper.getIds(crit, dtField, maxDate, maxWindowSize, useTimestamp);

        int i = 0;
        for (; i < ids.size(); ++i) {
            try {
                dao.findByPk(ids.get(i)).getPersister().remove();
            } catch (BeanNotFoundException e) {
                throw new PurgeFailedException(PurgeAPIMessages.FAILED.create(), e);
            }
        }

        return i;
    }

    /**
     * Exclui entradas usando persister. Deve ser usado quando nao for possivel excluir entidades usando bulk delete.
     * 
     * @param dao dao para encontrar entidades por id.
     * @param crit criteria para encontrar ids da janela.
     * @param dtField campo de data a usar para comparacao.
     * @param maxDate data de corte.
     * @param maxWindowSize tamanho da janela de expurgo.
     * @return itens excluidos.
     */
    public static int deleteWithPersister(BaseDAO<?> dao, Criteria crit, String dtField, Date maxDate, int maxWindowSize) {
        return deleteWithPersister(dao, crit, dtField, maxDate, maxWindowSize, true);
    }
}
